<div class={computedClass} aria-busy={loading} >
  {#if voted || expired }
    <ul class="poll-choices" aria-label="{intl.pollResults}">
      {#each options as option}
        <li class="poll-choice option">
          <div class="option-text">
            <strong>{option.share}%</strong> <span>{@html cleanTitle(option.title)}</span>
          </div>
          <svg aria-hidden="true">
            <line x1="0" y1="0" x2="{option.share}%" y2="0" />
          </svg>
        </li>
      {/each}
    </ul>
  {:else}
    <form class="poll-form" aria-label="{intl.voteOnPoll}" on:submit="onSubmit(event)" ref:form>
      <ul class="poll-choices" aria-label="{intl.pollChoices}">
        {#each options as option, i}
          <li class="poll-choice poll-form-option">
            <label>
              <input type="{multiple ? 'checkbox' : 'radio'}"
                     name="poll-choice-{uuid}"
                     value="{i}"
                     on:change="onChange()"
              >
              <span>{@html cleanTitle(option.title)}</span>
            </label>
          </li>
        {/each}
      </ul>
      <button disabled={formDisabled} type="submit">{intl.vote}</button>
    </form>
  {/if}
  <ul class="poll-details" aria-label="{intl.pollDetails}">
    <li class="poll-stat {notification ? 'is-notification' : ''}">
      <SvgIcon className="poll-icon" href="#fa-bar-chart" />
      <span class="poll-stat-text">{votesText}</span>
    </li>
    <li class="poll-stat {notification ? 'is-notification' : ''}">
      <SvgIcon className="poll-icon" href="#fa-clock" />
      <span class="poll-stat-text poll-stat-expiry">
        <span class="{useNarrowSize ? 'sr-only' : ''}">{expiryText}</span>
        <time datetime={expiresAt} title={expiresAtAbsoluteFormatted}>
          {expiresAtTimeagoFormatted}
        </time>
      </span>
    </li>
    <li class="poll-stat {notification ? 'is-notification' : ''} {expired ? 'poll-expired' : ''}">
      <button id={refreshElementId}
              class="focus-fix"
              aria-label="{intl.refresh}">
        <SvgIcon className="poll-icon" href="#fa-refresh" />
        <span class="poll-stat-text poll-stat-text-refresh" aria-hidden="true">
          {intl.refresh}
        </span>
      </button>
    </li>
  </ul>
</div>
<style>
  .poll {
    grid-area: poll;
    margin: 10px 10px 10px 5px;
    padding: 20px;
    border: 1px solid var(--main-border);
    border-radius: 2px;
    transition: opacity 0.2s linear;
    display: none;
  }

  .poll.shown {
    display: block;
  }

  .poll.status-in-own-thread {
    padding: 20px;
  }

  .poll.poll-loading {
    opacity: 0.5;
    pointer-events: none;
  }

  ul.poll-choices {
    list-style: none;
    margin: 0;
    padding: 0;
  }

  li.poll-choice {
    margin: 10px 0;
    padding: 0;
  }

  li.poll-choice:first-child {
    margin-top: 0;
  }

  .option {
    margin: 0 0 10px 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    stroke: var(--svg-fill);
    stroke-width: 10px;
  }

  .option-text {
    word-wrap: break-word;
    white-space: pre-wrap;
    font-size: 1.1em;
  }

  svg {
    height: 10px;
    width: 100%;
    margin-top: 5px;
  }

  .status-in-notification .option-text {
    color: var(--very-deemphasized-text-color);
  }

  .status-in-notification svg {
    stroke: var(--very-deemphasized-text-color);
  }

  .status-in-own-thread .option-text {
    font-size: 1.2em;
  }

  ul.poll-details {
    display: grid;
    grid-template-columns: max-content minmax(0, max-content) max-content;
    grid-gap: 20px;
    align-items: center;
    justify-content: left;
    margin: 10px 0 0 0;
    padding: 0;
    list-style: none;
    overflow-x: hidden;
  }

  .poll-stat button {
    /* reset button styles */
    background: none;
    box-shadow: none;
    border: none;
    border-spacing: 0;
    margin: 0;
    padding: 0;
    font-size: inherit;
    font-weight: normal;
    text-align: left;
    text-decoration: none;
    text-indent: 0;
    display: flex;
    align-items: center;
  }

  .poll-stat button:hover {
    text-decoration: underline;
  }

  li.poll-stat {
    display: flex;
    flex-direction: row;
    align-items: center;
    color: var(--deemphasized-text-color);
    padding: 0;
    margin: 0;
  }

  .poll-stat.is-notification, .poll-stat.is-notification .poll-stat-text {
    color: var(--very-deemphasized-text-color);
  }

  :global(.poll-stat.is-notification .poll-icon) {
    fill: var(--very-deemphasized-text-color);
  }

  .poll-stat.poll-expired {
    display: none;
  }

  .poll-stat-text {
    margin-left: 5px;
    color: var(--deemphasized-text-color);
  }

  .poll-stat-expiry {
    word-wrap: break-word;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  :global(.poll-icon) {
    fill: var(--deemphasized-text-color);
    width: 18px;
    height: 18px;
    min-width: 18px;
  }

  .poll-form-option {
    padding-bottom: 10px;
  }

  .poll-form label span {
    text-overflow: ellipsis;
    overflow: hidden;
    word-break: break-word;
    white-space: pre-wrap;
    padding-left: 5px;
  }

  @media (max-width: 479px) {
    .poll {
      padding: 10px 5px;
    }
    .poll.status-in-own-thread {
      padding: 10px;
    }
    ul.poll-details {
      grid-gap: 5px;
      justify-content: space-between;
    }
  }

  @media (max-width: 320px) {
    .poll-stat-text-refresh {
      display: none; /* takes up too much space on small devices */
    }
    ul.poll-details {
      grid-gap: 2px;
    }
    .poll-stat-text {
      margin-left: 2px;
    }
    li.poll-choice {
      margin: 5px 0;
    }
  }
</style>
<script>
  import SvgIcon from '../SvgIcon.html'
  import { store } from '../../_store/store.js'
  import { formatTimeagoFutureDate, formatTimeagoDate } from '../../_intl/formatTimeagoDate.js'
  import { absoluteDateFormatter } from '../../_utils/formatters.js'
  import { registerClickDelegate } from '../../_utils/delegate.js'
  import { classname } from '../../_utils/classname.js'
  import { getPoll, voteOnPoll } from '../../_actions/polls.js'
  import escapeHtml from 'escape-html'
  import { emojifyText } from '../../_utils/emojifyText.js'
  import { formatIntl } from '../../_utils/formatIntl.js'

  const REFRESH_MIN_DELAY = 1000

  async function doAsyncActionWithDelay (func) {
    const start = Date.now()
    const res = await func()
    const timeElapsed = Date.now() - start
    if (timeElapsed < REFRESH_MIN_DELAY) {
      // If less than five seconds, then continue to show the loading animation
      // so it's clear that something happened.
      await new Promise(resolve => setTimeout(resolve, REFRESH_MIN_DELAY - timeElapsed))
    }
    return res
  }

  function getChoices (form, options) {
    const res = []
    for (let i = 0; i < options.length; i++) {
      if (form.elements[i].checked) {
        res.push(i)
      }
    }
    return res
  }

  export default {
    oncreate () {
      this.onRefreshClick = this.onRefreshClick.bind(this)
      const { refreshElementId } = this.get()
      registerClickDelegate(this, refreshElementId, this.onRefreshClick)
    },
    data: () => ({
      loading: false,
      choices: []
    }),
    store: () => store,
    computed: {
      pollId: ({ originalStatus }) => originalStatus.poll.id,
      poll: ({ originalStatus, $polls, pollId }) => $polls[pollId] || originalStatus.poll,
      options: ({ poll, originalStatusEmojis, $autoplayGifs, votersOrVotesCount }) => (
        poll.options.map(({ title, votes_count: optionsVotesCount }) => ({
          title: emojifyText(escapeHtml(title), originalStatusEmojis, $autoplayGifs),
          share: votersOrVotesCount ? Math.round(optionsVotesCount / votersOrVotesCount * 100) : 0
        }))
      ),
      votersCount: ({ poll }) => poll.voters_count,
      votesCount: ({ poll }) => poll.votes_count,
      // Misskey reports the "voters_count" as null, so just use the "votes_count"
      votersOrVotesCount: ({ votersCount, votesCount }) => typeof votersCount === 'number' ? votersCount : votesCount,
      voted: ({ poll }) => poll.voted,
      multiple: ({ poll }) => poll.multiple,
      expired: ({ poll }) => poll.expired,
      expiresAt: ({ poll }) => poll.expires_at,
      // Misskey can have polls that never end. These give expiresAt as null
      // Also, Mastodon v4+ uses ISO strings, whereas Mastodon pre-v4 used numbers
      expiresAtTS: ({ expiresAt }) => (
        (typeof expiresAt === 'number' || typeof expiresAt === 'string') ? new Date(expiresAt).getTime() : null
      ),
      expiresAtTimeagoFormatted: ({ expiresAtTS, expired, $now }) => (
        expired ? formatTimeagoDate(expiresAtTS, $now) : formatTimeagoFutureDate(expiresAtTS, $now)
      ),
      expiresAtAbsoluteFormatted: ({ expiresAtTS }) => absoluteDateFormatter().format(expiresAtTS),
      expiryText: ({ expired }) => expired ? 'intl.expired' : 'intl.expires',
      refreshElementId: ({ uuid }) => `poll-refresh-${uuid}`,
      useNarrowSize: ({ $isMobileSize, expired, isStatusInOwnThread }) => (
        !isStatusInOwnThread && $isMobileSize && !expired
      ),
      formDisabled: ({ choices }) => !choices.length,
      votesText: ({ votersOrVotesCount }) => (
        formatIntl('intl.voteCount', { count: votersOrVotesCount })
      ),
      computedClass: ({ isStatusInNotification, isStatusInOwnThread, loading, shown }) => (
        classname(
          'poll',
          isStatusInNotification && 'status-in-notification',
          isStatusInOwnThread && 'status-in-own-thread',
          loading && 'poll-loading',
          shown && 'shown'
        )
      )
    },
    methods: {
      onRefreshClick () {
        (async () => {
          const { pollId } = this.get()
          this.set({ loading: true })
          try {
            const poll = await doAsyncActionWithDelay(() => getPoll(pollId))
            if (poll) {
              const { polls } = this.store.get()
              polls[pollId] = poll
              this.store.set({ polls })
            }
          } finally {
            this.set({ loading: false })
          }
        })()
        return true
      },
      async onSubmit (e) {
        e.preventDefault()
        e.stopPropagation()
        const { pollId, options, formDisabled } = this.get()
        if (formDisabled) {
          return
        }
        const choices = getChoices(this.refs.form, options)
        this.set({ loading: true })
        try {
          const poll = await doAsyncActionWithDelay(() => voteOnPoll(pollId, choices))
          if (poll) {
            const { polls } = this.store.get()
            polls[pollId] = poll
            this.store.set({ polls })
            // the height of the status changes after you vote on the poll
            requestAnimationFrame(() => this.fire('recalculateHeight'))
          }
        } finally {
          this.set({ loading: false })
        }
      },
      onChange () {
        const { options } = this.get()
        const choices = getChoices(this.refs.form, options)
        this.set({ choices })
      }
    },
    helpers: {
      cleanTitle (title) {
        // Remove newlines and tabs.
        // Mastodon UI doesn't care because in CSS it's formatted to be single-line, but we care
        // if people somehow insert newlines, because it can really mess up the formatting.
        return (title && title.replace(/[\n\t]+/g, ' ')) || ''
      }
    },
    components: {
      SvgIcon
    }
  }
</script>
